{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 12.3 Techniques for Method Chaining（方法链接的技巧）\n",
    "\n",
    "对序列进行转换的时候，我们会发现会创建很多再也不会用到的临时变量（temporary variable）。比如下面的例子：\n",
    "\n",
    "    df = load_data()\n",
    "    df2 = df[df['col2'] < 0]\n",
    "    df2['col1_demeaned'] = df2['col1'] - df2['col1'].mean()\n",
    "    result = df2.groupby('key').col1_demeaned.std()\n",
    "    \n",
    "这里我们不使用任何真实数据，这个例子说明了一些新方法。首先，DataFrame.assign方法是一个函数，它可以作为列赋值方法`df[k] = v`的替代品。它不会修改原有的对象，而是会返回一个带有修改标识的新DataFrame对象。所以下面两个方法是相等的：\n",
    "\n",
    "    # Usual non-functional way \n",
    "    df2 = df.copy() \n",
    "    df2['k'] = v\n",
    "\n",
    "    # Functional assign way \n",
    "    df2 = df.assign(k=v)\n",
    "\n",
    "在原始对象上直接进行赋值比用assign会更快一些，但是assign可以使用更方便的方法链接（method chaining）:\n",
    "\n",
    "    result = (df2.assign(col1_demeaned=df2.col1 - df2.col2.mean())\n",
    "             .groupby('key')\n",
    "             .col1_demeaned.std())\n",
    "\n",
    "需要记住的是，当使用方法链接的时候，你可能会需要引用临时对象。在之后的例子，我们不能引用load_data的结果，除非它被赋值给临时变量df. 。为了做到这一点，assign和其他一些pandas函数接受像函数一样函数参数（function-like arguments），也被称作为可调用（callables）。\n",
    "\n",
    "为了演示callables，考虑上面例子里的一个片段：\n",
    "\n",
    "    df = load_data() \n",
    "    df2 = df[df['col2'] < 0]\n",
    "\n",
    "这句可以被写为：\n",
    "\n",
    "    df = (load_data()[lambda x: x['col2'] < 0])\n",
    "\n",
    "在这里，load_data的结果没有赋值给参数，所以传入`[]`中的函数被绑定到了绑定到了在某个链接状态下的对象上（so the function passed into [] is then bound to the object at that stage of the method chain）。\n",
    "\n",
    "我们可以把整个序列携程一行链接表达式：\n",
    "\n",
    "    result = (load_data()\n",
    "              [lambda x: x.col2 < 0]\n",
    "              .assign(col1_demeaned=lambda x: x.col1 - x.col1.mean())\n",
    "              .groupby('key')\n",
    "              .col1_demeaned.std())\n",
    "\n",
    "我们可以把代码写成这种风格，但也可以分解成为步来写，这样可读性会更高。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1 The pipe Method（pipe方法）\n",
    "\n",
    "我们可以利用pandas内建的函数和一些用callables实现的方法链接，做很多事情。不过，有时候我们想要用自己的函数或一些第三方库里的函数。这就是pipe方法出现的原因。\n",
    "\n",
    "假设有一系列函数调用：\n",
    "\n",
    "    a = f(df, arg1=v1) \n",
    "    b = g(a, v2, arg3=v3) \n",
    "    c = h(b, arg4=v4)\n",
    "    \n",
    "当使用函数来接受或返回Series或DataFrame对象的时候，我们可以把上面的利用pipe重写为：\n",
    "\n",
    "    result = (df.pipe(f, arg1=v1)\n",
    "              .pipe(g, v2, arg3=v3) \n",
    "              .pipe(h, arg4=v4))\n",
    "\n",
    "f(df)和df.pipe(f)是一样的，但是pipe能让链接调用更简单。\n",
    "\n",
    "pipe一个有用的模式是生成一系列可重复的函数操作。例如，考虑计算两个组的平均值的差：\n",
    "\n",
    "    g = df.groupby(['key1', 'key2']) \n",
    "    df['col1'] = df['col1'] - g.transform('mean')\n",
    "    \n",
    "假设我们想要能对不止一个组进行减值，只要改变分组键（group key）即可。除此之外，我们可能想要把这种转换用方法链接的形式实现。这里有一个例子：\n",
    "\n",
    "    def group_demean(df, by, cols):\n",
    "        result = df.copy() \n",
    "        g = df.groupby(by) \n",
    "        for c in cols:\n",
    "            result[c] = df[c] - g[c].transform('mean') \n",
    "        return result\n",
    "        \n",
    "上面的也可以写为：\n",
    "\n",
    "    result = (df[df.col1 < 0] \n",
    "              .pipe(group_demean, ['key1', 'key2'], ['col1']))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [py35]",
   "language": "python",
   "name": "Python [py35]"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
